vickcoo

iOS Developer

將記憶體煩惱留給ARC

自動引用計數(Automatic Reference Counting,簡稱ARC)是Swift語言的一個關鍵功能,它負責管理物件在記憶體中的生命週期,以確保記憶體資源能夠被有效地分配和釋放。ARC是Swift的一個重要特性,使開發者無需手動管理記憶體,從而減少了記憶體洩漏和釋放不當的風險。

  • Swift會用它自動管理記憶體,無需我們手動操作,但你確實要考慮物件之間的關係以避免記憶體洩漏(Memory Leak)
  • 每次建立一個類別的實例時,ARC會分配一塊記憶體來儲存關於這個實例的所有資料
  • ARC會在Lifetime結束時自動釋放(deallocate)記憶體中的物件

ARC工作原理

它的運作方式很簡單,當有一個物件被創立的時候,它的ARC會初始化為1,當有新的引用指向這個物件時就會加1,當一個引用不再存在時ARC就會減1,很簡單的加減法吧!

這邊用兩種方式寫個故事幫助你們瞭解

文字版

有一天,David走在路上,看到了閃亮的iPhone XR。他被它的功能和外觀所吸引,決定拿走了它並成為了它的持有者。這個時候,因為David引用了iPhone XRiPhone的ARC增加到2。

過了一會,John也走過來,他看到了這支iPhone XR,並說這支手機是他的,因此他要求對方將它交給John。現在,iPhone的ARC變成了3。

然而,這個情況並不持久。不久後,一位警察出現,調查了手機的來源,發現這支iPhone根本不屬於David或John。他們兩人被告知不能再持有這支手機,因為它並不屬於他們中的任何一個。於是,David和John都取消了對iPhone的引用,ARC迅速降為1。

最後也不知道這支手機是誰的,所以決定進行銷毀,iPhone XR也取消了對iPhone的引用,所以ARC歸零從此就消失在這個世界上(從記憶體中釋放)。

程式碼版

故事中用到的PhonePerson的類別可以至下一段程式碼查看

//有一天,David走在路上,看到了閃亮的`iPhone XR`。他被它的功能和外觀所吸引,決定拿走了它並成為了它的持有者。這個時候,因為David引用了`iPhone XR`iPhone的ARC增加到2。
var iPhone: Phone? = Phone(name: "iPhone XR") // ARC = 1
var david: Person = Person(name: "David")
david.phone = iPhone // ARC = 2

//過了一會,John也走過來,他看到了這支`iPhone XR`,並說這支手機是他的,因此他要求對方將它交給John。現在,iPhone的ARC變成了3。
var john: Person = Person(name: "John")
john.phone = iPhone // ARC = 3

//然而,這個情況並不持久。不久後,一位警察出現,調查了手機的來源,發現這支iPhone根本不屬於David或John。他們兩人被告知不能再持有這支手機,因為它並不屬於他們中的任何一個。於是,David和John都取消了對iPhone的引用,ARC迅速降為1。
david.phone = nil // ARC = 2
john.phone = nil // ARC = 1

//最後也不知道這支手機是誰的,所以決定進行銷毀,`iPhone XR`也取消了對iPhone的引用,所以ARC歸零從此就消失在這個世界上(從記憶體中釋放)。
iPhone = nil // ARC = 0
class Person {
    var name: String
    var phone: Phone?
    init (name: String) {
        self.name = name
    }
}

class Phone {
    var name: String
    init(name: String) {
        self.name = name
    }
}

Retain Cycle是什麼,怎麼發生的?

Retain Cycle就是指物件存在記憶體中,但是不知道什麼原因所以無法被釋放。 當兩個物件互相引用時就有可能會發生Retain Cycle,以下面的例子來說明好了,可以邊看邊對照。

故事版

在遙遠的宇宙中,有兩顆星星。這兩顆星星互相吸引,彷彿注定要一起閃耀,但他們的相遇並不如表面上看起來那麼簡單。

我跟女朋友很喜歡觀星,有天我們找到兩顆超漂亮的星星,我們決定把它們變成我們的並且取名為ShellyBen,這兩顆星星互相吸引,我感覺它們有某種連結就像是對情侶,所以他們在宇宙中建立了一條神秘的聯繫,這就像是將他們的光芒結合在一起。

某一天我跟女朋友已經不喜歡觀星了,決定不再管那些星星,斷開我們跟星星之間的結繫,但星星的光芒並沒有消失。這是因為這兩顆星星在彼此間建立了強大的連結,就像他們在宇宙中留下了一條微弱的光芒之線,使它們互相引用。

儘管我們不再觀察這些星星也找不到它們,但這些光芒仍然存在,因為ARC保持著它們的存在。它們將永遠閃耀在宇宙中,無法再被引用,就像永遠無法再見面的舊情人。

這個故事反映了ARC的運作原理。即使物件之間的引用關係已經斷開,但當ARC還保持對它們的引用時,它們將繼續存在,無法被釋放。ARC確保了記憶體的正確管理,但也提醒我們要謹慎處理引用,以避免出現意外的情況。

程式碼版

故事中用到的StarPerson的類別可以至下一段程式碼查看

var me: Person = Person(name: "me")
var girlfriend: Person = Person(name: "girlfriend")
// 我跟女朋友很喜歡觀星,有天我們各自找到超漂亮的星星,我們決定把那些星星變成我們的並且取名為`Shelly`跟`Ben`
var shelly: Star = Star(name: "Shelly") // shelly ARC = 1
var ben: Star = Star(name: "Ben") // ben ARC = 1

me.star = ben
girlfriend.star = shelly

// 這兩顆星星互相吸引,我感覺它們有某種連結就像是對情侶,所以他們在宇宙中建立了一條神秘的聯繫,這就像是將他們的光芒結合在一起。
ben.linkStar = shelly // ben ARC = 2
shelly.linkStar = ben // shelly ARC = 2

// 某一天我跟女朋友已經不喜歡觀星了,決定不再管那些星星,斷開我們跟星星之間的結繫
me.star = nil // ben ARC = 1
girlfriend.star = nil // shelly ARC = 1

// 但星星的光芒並沒有消失。這是因為這兩顆星星在彼此間建立了強大的連結,就像他們在宇宙中留下了一條微弱的光芒之線,使它們互相引用。
// 儘管我們不再觀察這些星星也找不到它們,但這些光芒仍然存在,因為ARC保持著它們的存在。它們將永遠閃耀在宇宙中,無法再被引用,就像永遠無法再見面的舊情人。
// 這個故事反映了自動引用計數(ARC)的運作原理。即使物件之間的引用關係已經斷開,但當ARC還保持對它們的引用時,它們將繼續存在,無法被釋放。ARC確保了記憶體的正確管理,但也提醒我們要謹慎處理引用,以避免出現意外的情況。
class Person {
    var name: String
    var star: Star?
    init(name: String) {
        self.name = name
    }
}

class Star {
    var name: String
    var linkStar: Star?
    init(name: String) {
        self.name = name
    }
}

如何避免Memory Leak

前面說的Retain Cycle就可能會造成Memory Leak

  • Retain Cycle就是兩物件之間強引用(Strong Reference)
  • Memory Leak就是物件存在記憶體中,但是程式已經存取不到也無法釋放

這邊要介紹兩個Keyword來解決問題,這兩個方法的核心原理都是這個物件不會加入ARC進行計數, 第一個是weak,就在宣告變數的前面加上weak就好了!

weak var star: Star?

第二個是unowned,跟weak差在optional的部分,當你確認這個物件在它生命週期結束前都不會變成nil就可以使用它

unowned var star: Star

簡單來說

  • weak用來修飾optional
  • unowned用來修飾non-optional

Reference